package SeamCarving;

import java.util.*; 
import org.eclipse.swt.graphics.*;

import CommonInterfaces.*;
import Containers.*;
import Drawers.*;

public class DynamicEnergyMap
{
	private ListedMatrix<Cell> _matrix;
	private Vector<Seam<Cell>> _vectorOfSeams;
	
	class Cell implements IsCloneable
	{
		public int Energy, TotalEnergy, Color;
		public ListedMatrix<Cell>.ListedMatrixIter NextInSeam;
		
		public Cell(int color)
		{
			Color = color;
		}
		
		private Cell(int color, int energy, int totalEnergy, ListedMatrix<Cell>.ListedMatrixIter nextInSeam)
		{
			Energy = energy;
			Color = color;
			TotalEnergy = totalEnergy;
			NextInSeam = nextInSeam;
		}
		
		public Object clone()
		{
			return new Cell(Energy, Color, TotalEnergy, NextInSeam);
		}
	}
	
	private int colorDiff (int color1, int color2)
	{
		return (
			Math.abs((color1 & 0xFF) - (color2 & 0xFF))            + 
			Math.abs(((color1 & 0xFF00) - (color2 & 0xFF00)) >> 8) + 
			Math.abs((color1 & 0xFF0000) - (color2 & 0xFF0000) >> 16));
	}
	
	private int GetEnergy(ListedMatrix<Cell>.ListedMatrixIter iter)
	{
		int pixelColor = iter.GetValue().Color;
		int energy = 0;
		
		ListedMatrix<Cell>.ListedMatrixIter tempIter;
		
		tempIter = iter.clone().MoveLeft();
		if (!tempIter.equals(_matrix.End()))
		{
			energy += colorDiff(pixelColor, tempIter.GetValue().Color);
			if (!tempIter.MoveDown().equals(_matrix.End()))
					energy += colorDiff(pixelColor, tempIter.GetValue().Color);
			if (!(tempIter = iter.clone()).MoveUp().equals(_matrix.End()))
					energy += colorDiff(pixelColor, tempIter.GetValue().Color);
		}
		
		tempIter = iter.clone();
		energy +=
			(((tempIter = iter.clone()).MoveDown().equals(_matrix.End())) ? 0 : colorDiff(pixelColor, tempIter.GetValue().Color)) +
			(((tempIter = iter.clone()).MoveUp().equals(_matrix.End())) ? 0 : colorDiff(pixelColor, tempIter.GetValue().Color));

		tempIter = iter.clone().MoveRight();
		if (!tempIter.equals(_matrix.End()))
		{
			energy += colorDiff(pixelColor, tempIter.GetValue().Color);
			if (!tempIter.MoveDown().equals(_matrix.End()))
					energy += colorDiff(pixelColor, tempIter.GetValue().Color);
			if (!(tempIter = iter.clone()).MoveUp().equals(_matrix.End()))
					energy += colorDiff(pixelColor, tempIter.GetValue().Color);
		}
		
		return energy;
	}
	
	public DynamicEnergyMap(ImageData image)
	{
		_matrix = new ListedMatrix<Cell>(image.width, image.height);
		
		int x = 0, y = 0;
		for (ListedMatrix<Cell>.ListedMatrixIter yIter = _matrix.Begin(); !yIter.equals(_matrix.End()); yIter.MoveDown(), y++, x = 0)
			for (ListedMatrix<Cell>.ListedMatrixIter xIter = yIter.clone(); !xIter.equals(_matrix.End()); xIter.MoveRight(), x++)
				xIter.SetValue(new Cell(image.getPixel(x, y)));
	}
	
	private ListedMatrix<Cell>.ListedMatrixIter FindNthEnergy(boolean isVertical, int n)
	{
		class Sorted implements Comparable<Sorted>
		{
			ListedMatrix<Cell>.ListedMatrixIter iter;
			
			public int compareTo(Sorted i)
			{
				int t = i.iter.GetValue().TotalEnergy;
				if (iter.GetValue().TotalEnergy > t)
					return 1;
				else if (iter.GetValue().TotalEnergy < i.iter.GetValue().TotalEnergy)
					return -1;
				else
					return 0;
			}
		}
		
		Sorted[] sorter = new Sorted[isVertical ? _matrix.numOfCols() : _matrix.numOfRows()];
		
		/*
		ListedMatrix<Cell>.ListedMatrixIter temp = _matrix.Begin();

		
		System.out.println(_matrix.numOfRows());
		for (int i = 0; i < _matrix.numOfRows(); i++, temp.MoveRight(isVertical))
		{
			int j = temp.GetValue().TotalEnergy;
			System.out.println(i);
		}
		
		*/
		int index = 0;
		for (ListedMatrix<Cell>.ListedMatrixIter iter = _matrix.Begin(); !iter.equals(_matrix.End()); iter.MoveRight(isVertical), index++)
		{
			sorter[index] = new Sorted();
			sorter[index].iter = iter.clone();
		}
		
		System.out.print("going in");
		System.out.println(n);
		System.out.println(sorter.length);
		System.out.println("Tha matrix size is : " + _matrix.numOfCols() + " X " + _matrix.numOfRows());
		Arrays.sort(sorter);
		return sorter[n].iter;
	}
	
	private Seam<Cell> GetNthSeam(boolean isVertical, int n)
	{
		Seam<Cell> ret = new Seam<Cell>();
		ret.Elems.addLast(FindNthEnergy(isVertical, n));
		
		while (!ret.Elems.getLast().GetValue().NextInSeam.equals(_matrix.End()))
			ret.Elems.addLast(ret.Elems.getLast().GetValue().NextInSeam.clone());
			
		return ret;
	}
	
	private int AvrageColor (int color1, int color2)
	{
		return (((((color1 & 0xFF)+ (color2 & 0xFF)) / 2) & 0xFF) | 
				((((color1 & 0xFF00)+ (color2 & 0xFF00)) / 2) & 0xFF00)| 
				((((color1 & 0xFF0000)+ (color2 & 0xFF0000)) / 2) & 0xFF0000));
	}
	
	private void AvragizeSeamColorAndEnergy(boolean isVertical, Seam<Cell> seam)
	{
		for (ListedMatrix<Cell>.ListedMatrixIter elem : seam.Elems)
		{
			if (elem.clone().MoveRight(isVertical).equals(_matrix.End()))
				elem.SetValue(new Cell(elem.clone().MoveLeft(isVertical).GetValue().Color));
			else if (elem.clone().MoveLeft(isVertical).equals(_matrix.End()))
				elem.SetValue(new Cell(elem.clone().MoveRight(isVertical).GetValue().Color));
			else
				elem.SetValue(new Cell((AvrageColor(
						elem.clone().MoveRight(isVertical).GetValue().Color, 
						elem.clone().MoveLeft(isVertical).GetValue().Color))));
		}
		
		UpdateEnergyOnSeam(seam, isVertical);
		UpdateEnergyOnSeamNeighbors(seam, isVertical);
	}
	
	private void UpdateEnergyOnSeam (Seam<Cell> seam, boolean isVertical)
	{
		for (ListedMatrix<Cell>.ListedMatrixIter iter : seam.Elems)
			iter.GetValue().Energy = GetEnergy(iter);
	}
	
	private void UpdateEnergyOnSeamNeighbors(Seam<Cell> seam, boolean isVertical)
	{
		for (ListedMatrix<Cell>.ListedMatrixIter iter : seam.Elems)
		{
			ListedMatrix<Cell>.ListedMatrixIter temp = iter.clone().MoveLeft(isVertical);
			if (!temp.equals(_matrix.End()))
				temp.GetValue().Energy = GetEnergy(temp);
				
			temp = iter.clone().MoveRight(isVertical);
			if (!temp.equals(_matrix.End()))
				temp.GetValue().Energy = GetEnergy(temp);
		}
	}
	
	private Seam<Cell> GetNthSeamToAdd(boolean isVertical, int n)
	{
		Seam<Cell> ret = _matrix.DuplicateSeam(GetNthSeam(isVertical, n), isVertical);
		AvragizeSeamColorAndEnergy(isVertical, ret);
		return ret;
	}
	
	private Seam<Cell> AddNthSeam(boolean isVertical, int n)
	{
		Seam<Cell> ret = GetNthSeamToAdd(isVertical, n);
		_matrix.AddSeam(ret, isVertical);
		return ret;
	}
	
	public void InitSeamVector (boolean isVertical, int maxSize)
	{
		int imageSize = (isVertical ? _matrix.numOfCols() : _matrix.numOfRows());
		
		_vectorOfSeams = new Vector<Seam<Cell>>(maxSize);
		_vectorOfSeams.setSize(_vectorOfSeams.capacity());
		
		final double fraction = 0.3;
		
		int index;
		
		CalcDynamicMap(isVertical, true);
		
		for (index = imageSize; index > maxSize; index--)
		{
			CalcDynamicMap(isVertical, false);
			Seam<Cell> seamToRemove = GetNthSeam(isVertical, 0);
			_matrix.RemoveSeam(seamToRemove, isVertical);
			UpdateEnergyOnSeamNeighbors(seamToRemove, isVertical);
		}
		
		if (imageSize > maxSize)
			imageSize = maxSize;
		
		for (index = imageSize - 1; index > 0; index--)
		{
			CalcDynamicMap(isVertical, false);
			_vectorOfSeams.set(index, GetNthSeam(isVertical, 0));
			_matrix.RemoveSeam(_vectorOfSeams.get(index), isVertical);
			UpdateEnergyOnSeamNeighbors(_vectorOfSeams.get(index), isVertical);
		}
		
		for (index = 1; index < imageSize; index++)
			_matrix.AddSeam(_vectorOfSeams.get(index), isVertical);
		
		CalcDynamicMap(isVertical, true);
		
		for (int tempImageSize = imageSize; tempImageSize < maxSize; tempImageSize = (int)Math.min(maxSize, (double)tempImageSize * (double)(1 + fraction)))
		{
			for (int size = tempImageSize, seamPos = 0; size < Math.min(maxSize, (double)tempImageSize * (double)(1 + fraction)); size++, seamPos += 3)
			{
				CalcDynamicMap(isVertical, false);
				_vectorOfSeams.set(size, AddNthSeam(isVertical, seamPos)); // 1'st then 3'rd 5'th and so on...
				UpdateEnergyOnSeamNeighbors(_vectorOfSeams.get(size), isVertical);
				UpdateEnergyOnSeam(_vectorOfSeams.get(size), isVertical);
			}
		}

		for (index = maxSize - 1; index >= imageSize; index--)
			_matrix.RemoveSeam(_vectorOfSeams.get(index), isVertical);
		

		System.out.println("Tha matrix size is : " + _matrix.numOfCols() + " X " + _matrix.numOfRows());
	}
	
	private void CalcDynamicMap(boolean isVertical, boolean calcEnergy)
	{
		if (calcEnergy)
		{
			for (ListedMatrix<Cell>.ListedMatrixIter yIter = _matrix.Begin(); !yIter.equals(_matrix.End()); yIter.MoveDown())
				for (ListedMatrix<Cell>.ListedMatrixIter xIter = yIter.clone(); !xIter.equals(_matrix.End()); xIter.MoveRight())
					xIter.GetValue().Energy = GetEnergy(xIter);
		}
		
		ListedMatrix<Cell>.ListedMatrixIter yIter = _matrix.Begin(), xIter, xIter2;
		
		for (int i = 1; i < (isVertical ? _matrix.numOfRows() : _matrix.numOfCols()); i++)
			yIter.MoveDown(isVertical);
		
		//first line
		for (xIter = yIter.clone(); !xIter.equals(_matrix.End()); xIter.MoveRight(isVertical))
		{
			xIter.GetValue().TotalEnergy = xIter.GetValue().Energy;
			xIter.GetValue().NextInSeam = _matrix.End();
		}
		
		if (yIter.MoveUp(isVertical).equals(_matrix.End())) // if this is a one line matrix
			return;
			
		for (; !yIter.equals(_matrix.End()); yIter.MoveUp(isVertical))
		{
			xIter = yIter.clone();
			xIter2 = yIter.clone();
			xIter2.MoveDown(isVertical);
			
			xIter.GetValue().TotalEnergy = xIter.GetValue().Energy + xIter2.GetValue().TotalEnergy;
			xIter.GetValue().NextInSeam = xIter2.clone();
			
			if (!xIter2.clone().MoveRight(isVertical).equals(_matrix.End()))
				if (xIter.GetValue().TotalEnergy > xIter.GetValue().Energy +
					xIter2.MoveRight(isVertical).GetValue().TotalEnergy)
				{
					xIter.GetValue().TotalEnergy = xIter.GetValue().Energy + xIter2.GetValue().TotalEnergy;
					xIter.GetValue().NextInSeam = xIter2.clone();
				}
				
			for (xIter.MoveRight(isVertical); !xIter.equals(_matrix.End()); xIter.MoveRight(isVertical))
			{
				xIter2 = xIter.clone();
				xIter2.MoveDown(isVertical).MoveLeft(isVertical);
				
				xIter.GetValue().TotalEnergy = xIter.GetValue().Energy + xIter2.GetValue().TotalEnergy;
				xIter.GetValue().NextInSeam = xIter2.clone();
				
				xIter2.MoveRight(isVertical);
				
				if (xIter.GetValue().TotalEnergy > xIter.GetValue().Energy + xIter2.GetValue().TotalEnergy)
				{
					xIter.GetValue().TotalEnergy = xIter.GetValue().Energy + xIter2.GetValue().TotalEnergy;
					xIter.GetValue().NextInSeam = xIter2.clone();
				}

				xIter2.MoveRight(isVertical);
				
				if (!xIter2.equals(_matrix.End()))
					if (xIter.GetValue().TotalEnergy > xIter.GetValue().Energy + 
						xIter2.GetValue().TotalEnergy)
					{
						xIter.GetValue().TotalEnergy = xIter.GetValue().Energy + xIter2.GetValue().TotalEnergy;
						xIter.GetValue().NextInSeam = xIter2.clone();
					}
			}
		}
	}
	
	public void makeSize (Point newSize, boolean isVertical)
	{
		int newX = (isVertical) ? newSize.x : newSize.y, oldX = (isVertical) ? _matrix.numOfCols() : _matrix.numOfRows();

		if (oldX < newX)
			for (int i = oldX; i < newX; i++)
				_matrix.AddSeam(_vectorOfSeams.get(i), isVertical);
		else
			for (int i = oldX - 1; i >= newX; i--)
				_matrix.RemoveSeam(_vectorOfSeams.get(i), isVertical);
	}
	
	
	public void makeSize (Point newSize)
	{
		boolean xEnlarged = (newSize.x > _matrix.numOfCols()), yEnlarged = (newSize.y > _matrix.numOfRows());
		int newX = newSize.x, newY = newSize.y;
		int colsAdded = 0, rowsAdded = 0;
		Seam<Cell> tempSeam1, tempSeam2;
		
		while (newX != _matrix.numOfCols() && newY != _matrix.numOfRows())
		{
			CalcDynamicMap(true, true);
			if (xEnlarged)
				tempSeam1 = GetNthSeamToAdd(true, colsAdded);
			else 
				tempSeam1 = GetNthSeam(true, 0);
				
			CalcDynamicMap(false, false);
			if (yEnlarged)
				tempSeam2 = GetNthSeamToAdd(false, rowsAdded);
			else
				tempSeam2 = GetNthSeam(false, 0);
			
			if (tempSeam1.Elems.getFirst().GetValue().TotalEnergy / _matrix.numOfRows() < 
					tempSeam2.Elems.getFirst().GetValue().TotalEnergy / _matrix.numOfCols())
			{
				if (xEnlarged)
				{
					_matrix.AddSeam(tempSeam1, true);
					colsAdded += 2;
					UpdateEnergyOnSeam(tempSeam1, true);
				}
				else
					_matrix.RemoveSeam(tempSeam1, true);
				
				UpdateEnergyOnSeamNeighbors(tempSeam1, true);
			}
			else
			{
				if (yEnlarged)
				{
					_matrix.AddSeam(tempSeam2, false);
					rowsAdded += 2;
					UpdateEnergyOnSeam(tempSeam2, false);
				}
				else
					_matrix.RemoveSeam(tempSeam2, false);

				UpdateEnergyOnSeamNeighbors(tempSeam2, true);
			}
		}
		
		while (newX != _matrix.numOfCols())
		{
			CalcDynamicMap(true, true);
			if (xEnlarged)
			{
				tempSeam1 = GetNthSeamToAdd(true, colsAdded);
				_matrix.AddSeam(tempSeam1, true);
				colsAdded += 2;
				UpdateEnergyOnSeam(tempSeam1, true);
			}
			else
			{
				tempSeam1 = GetNthSeam(true, 0);
				_matrix.RemoveSeam(tempSeam1, true);
			}
			UpdateEnergyOnSeamNeighbors(tempSeam1, true);
		}
		
		while (newY != _matrix.numOfRows())
		{
			CalcDynamicMap(false, true);
			if (yEnlarged)
			{
				tempSeam1 = GetNthSeamToAdd(false, colsAdded);
				_matrix.AddSeam(tempSeam1, false);
				rowsAdded += 2;
				UpdateEnergyOnSeam(tempSeam1, false);
			}
			else
			{
				tempSeam1 = GetNthSeam(false, 0);
				_matrix.RemoveSeam(tempSeam1, false);
			}
			UpdateEnergyOnSeamNeighbors(tempSeam1, false);
		}
	}
	
	public void draw(Drawer drawer, Point newSize)
	{
		ListedMatrix<Cell>.ListedMatrixIter yIter, xIter;
		
		int iter, y, x;
		//int extraBytes = drawer.BytesPerLine() - _matrix.numOfCols() * 3;
		for (yIter = _matrix.Begin(), y = 0; y < newSize.y; yIter.MoveDown(), y++)
			for (xIter = yIter.clone(), iter = y * drawer.BytesPerLine(), x = 0; x < newSize.x; xIter.MoveRight(), iter += 3, x++)
			{
				int color = xIter.GetValue().Color;
				drawer.PutPixel(iter, (byte)((color & 0xFF0000) >> 16), (byte)((color & 0xFF00)  >> 8), (byte)(color & 0xFF));
			}
	}
	
	public Point CurrentSize()
	{
		return new Point(_matrix.numOfCols(), _matrix.numOfRows());
	}
}
